﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;

namespace Universe.IoC
{
    // TODO: Сообщения об ошибках для ID!=null
    public class ConfigurationContainer
    {
        public delegate T Func<T>();

        private const string DefaultId = null;

        private readonly IDictionary<ContractKey, Delegate> repository = new Dictionary<ContractKey, Delegate>();

        [ThreadStatic]
        private static Stack<ContractKey> _stack;

        [Serializable]
        public class ConfigurationException : Exception
        {
            public ConfigurationException()
            {
            }


            public ConfigurationException(string message)
                : base(message)
            {
            }


            public ConfigurationException(string message, Exception innerException)
                : base(message, innerException)
            {
            }


            protected ConfigurationException(SerializationInfo info, StreamingContext context)
                : base(info, context)
            {
            }
        }


        public void Register<TContract>(Func<TContract> function)
        {
            this.Register(function, DefaultId);
        }

        public void Register<TContract>(Func<TContract> function, string id)
        {
            repository[ContractKey.Create(id, typeof(TContract))] = function;
        }



        public T Resolve<T>()
        {
            T result;
            ResolveImpl<T>(DefaultId, out result);
            return result;
        }

        public T Resolve<T>(string id)
        {
            id.Length.ToString();
            T result;
            ResolveImpl<T>(id, out result);
            return result;
        }

        public T ResolveRequired<T>()
        {
            T result;
            if (!ResolveImpl<T>(DefaultId, out result))
                throw new ConfigurationException("Implementation of '" + typeof(T) + "' is not provided");

            return result;
        }

        public T ResolveRequired<T>(string id)
        {
            T result;
            if (!ResolveImpl<T>(id, out result))
                throw new ConfigurationException("Implementation of '" + typeof(T) + "' is not provided");

            return result;
        }

        private bool ResolveImpl<TContract>(string id, out TContract result)
        {
            var key = ContractKey.Create(id, typeof (TContract));

            if (_stack == null)
                _stack = new Stack<ContractKey>();

            if (_stack.Contains(key))
            {
                StringBuilder stack = new StringBuilder(typeof(TContract).ToString());
                foreach (var type in _stack)
                    stack.Append(" --> ").Append(type.ToString());

                throw new ConfigurationException(
                    "Circular reference detected by resolving '" + typeof (TContract) + "'. "
                    + "Stack is " + stack);
            }

            try
            {
                _stack.Push(key);


                Delegate @delegate;
                if (!repository.TryGetValue(key, out @delegate))
                {
                    result = default(TContract);
                    return false;
                }

                Func<TContract> func = (Func<TContract>) @delegate;
                result = func();
                return true;
            }
            finally
            {
                _stack.Pop();
            }
        }


        class ContractKey 
        {
            public string Id { get; set; }
            public Type Type { get; set; }

            public static ContractKey Create(string id, Type type)
            {
                ContractKey ret = new ContractKey();
                ret.Id = id;
                ret.Type = type;

                return ret;
            }

            public bool Equals(ContractKey other)
            {
                if (ReferenceEquals(null, other)) return false;
                if (ReferenceEquals(this, other)) return true;
                return Equals(other.Id, Id) && Equals(other.Type, Type);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != typeof (ContractKey)) return false;
                return Equals((ContractKey) obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return ((Id != null ? Id.GetHashCode() : 0)*397) ^ (Type != null ? Type.GetHashCode() : 0);
                }
            }
        }

    }
}
